/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.imageio;

import java.awt.Point;
import java.awt.Transparency;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.color.ColorSpace;
import java.awt.image.IndexColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Hashtable;

/**
 * A class that allows the format of an image (in particular, its
 * <code>SampleModel</code> and <code>ColorModel</code>) to be
 * specified in a convenient manner.
 */
public class ImageTypeSpecifier {

  /**
   * The <code>ColorModel</code> to be used as a prototype.
   */
  protected ColorModel colorModel;

  /**
   * A <code>SampleModel</code> to be used as a prototype.
   */
  protected SampleModel sampleModel;

  /**
   * Cached specifiers for all of the standard
   * <code>BufferedImage</code> types.
   */
  private static ImageTypeSpecifier[] BISpecifier;
  private static ColorSpace sRGB;

  // Initialize the standard specifiers
  static {
    sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);

    BISpecifier =
        new ImageTypeSpecifier[BufferedImage.TYPE_BYTE_INDEXED + 1];
  }

  /**
   * A constructor to be used by inner subclasses only.
   */
  private ImageTypeSpecifier() {
  }

  /**
   * Constructs an <code>ImageTypeSpecifier</code> directly
   * from a <code>ColorModel</code> and a <code>SampleModel</code>.
   * It is the caller's responsibility to supply compatible
   * parameters.
   *
   * @param colorModel a <code>ColorModel</code>.
   * @param sampleModel a <code>SampleModel</code>.
   * @throws IllegalArgumentException if either parameter is <code>null</code>.
   * @throws IllegalArgumentException if <code>sampleModel</code> is not compatible with
   * <code>colorModel</code>.
   */
  public ImageTypeSpecifier(ColorModel colorModel, SampleModel sampleModel) {
    if (colorModel == null) {
      throw new IllegalArgumentException("colorModel == null!");
    }
    if (sampleModel == null) {
      throw new IllegalArgumentException("sampleModel == null!");
    }
    if (!colorModel.isCompatibleSampleModel(sampleModel)) {
      throw new IllegalArgumentException
          ("sampleModel is incompatible with colorModel!");
    }
    this.colorModel = colorModel;
    this.sampleModel = sampleModel;
  }

  /**
   * Constructs an <code>ImageTypeSpecifier</code> from a
   * <code>RenderedImage</code>.  If a <code>BufferedImage</code> is
   * being used, one of the factory methods
   * <code>createFromRenderedImage</code> or
   * <code>createFromBufferedImageType</code> should be used instead in
   * order to get a more accurate result.
   *
   * @param image a <code>RenderedImage</code>.
   * @throws IllegalArgumentException if the argument is <code>null</code>.
   */
  public ImageTypeSpecifier(RenderedImage image) {
    if (image == null) {
      throw new IllegalArgumentException("image == null!");
    }
    colorModel = image.getColorModel();
    sampleModel = image.getSampleModel();
  }

  // Packed

  static class Packed extends ImageTypeSpecifier {

    ColorSpace colorSpace;
    int redMask;
    int greenMask;
    int blueMask;
    int alphaMask;
    int transferType;
    boolean isAlphaPremultiplied;

    public Packed(ColorSpace colorSpace,
        int redMask,
        int greenMask,
        int blueMask,
        int alphaMask, // 0 if no alpha
        int transferType,
        boolean isAlphaPremultiplied) {
      if (colorSpace == null) {
        throw new IllegalArgumentException("colorSpace == null!");
      }
      if (colorSpace.getType() != ColorSpace.TYPE_RGB) {
        throw new IllegalArgumentException
            ("colorSpace is not of type TYPE_RGB!");
      }
      if (transferType != DataBuffer.TYPE_BYTE &&
          transferType != DataBuffer.TYPE_USHORT &&
          transferType != DataBuffer.TYPE_INT) {
        throw new IllegalArgumentException
            ("Bad value for transferType!");
      }
      if (redMask == 0 && greenMask == 0 &&
          blueMask == 0 && alphaMask == 0) {
        throw new IllegalArgumentException
            ("No mask has at least 1 bit set!");
      }
      this.colorSpace = colorSpace;
      this.redMask = redMask;
      this.greenMask = greenMask;
      this.blueMask = blueMask;
      this.alphaMask = alphaMask;
      this.transferType = transferType;
      this.isAlphaPremultiplied = isAlphaPremultiplied;

      int bits = 32;
      this.colorModel =
          new DirectColorModel(colorSpace,
              bits,
              redMask, greenMask, blueMask,
              alphaMask, isAlphaPremultiplied,
              transferType);
      this.sampleModel = colorModel.createCompatibleSampleModel(1, 1);
    }
  }

  /**
   * Returns a specifier for a packed image format that will use a
   * <code>DirectColorModel</code> and a packed
   * <code>SampleModel</code> to store each pixel packed into in a
   * single byte, short, or int.
   *
   * @param colorSpace the desired <code>ColorSpace</code>.
   * @param redMask a contiguous mask indicated the position of the red channel.
   * @param greenMask a contiguous mask indicated the position of the green channel.
   * @param blueMask a contiguous mask indicated the position of the blue channel.
   * @param alphaMask a contiguous mask indicated the position of the alpha channel.
   * @param transferType the desired <code>SampleModel</code> transfer type.
   * @param isAlphaPremultiplied <code>true</code> if the color channels will be premultipled by the
   * alpha channel.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>colorSpace</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>colorSpace</code> is not of type
   * <code>TYPE_RGB</code>.
   * @throws IllegalArgumentException if no mask has at least 1 bit set.
   * @throws IllegalArgumentException if <code>transferType</code> if not one of
   * <code>DataBuffer.TYPE_BYTE</code>, <code>DataBuffer.TYPE_USHORT</code>, or
   * <code>DataBuffer.TYPE_INT</code>.
   */
  public static ImageTypeSpecifier
  createPacked(ColorSpace colorSpace,
      int redMask,
      int greenMask,
      int blueMask,
      int alphaMask, // 0 if no alpha
      int transferType,
      boolean isAlphaPremultiplied) {
    return new ImageTypeSpecifier.Packed(colorSpace,
        redMask,
        greenMask,
        blueMask,
        alphaMask, // 0 if no alpha
        transferType,
        isAlphaPremultiplied);
  }

  static ColorModel createComponentCM(ColorSpace colorSpace,
      int numBands,
      int dataType,
      boolean hasAlpha,
      boolean isAlphaPremultiplied) {
    int transparency =
        hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE;

    int[] numBits = new int[numBands];
    int bits = DataBuffer.getDataTypeSize(dataType);

    for (int i = 0; i < numBands; i++) {
      numBits[i] = bits;
    }

    return new ComponentColorModel(colorSpace,
        numBits,
        hasAlpha,
        isAlphaPremultiplied,
        transparency,
        dataType);
  }

  // Interleaved

  static class Interleaved extends ImageTypeSpecifier {

    ColorSpace colorSpace;
    int[] bandOffsets;
    int dataType;
    boolean hasAlpha;
    boolean isAlphaPremultiplied;

    public Interleaved(ColorSpace colorSpace,
        int[] bandOffsets,
        int dataType,
        boolean hasAlpha,
        boolean isAlphaPremultiplied) {
      if (colorSpace == null) {
        throw new IllegalArgumentException("colorSpace == null!");
      }
      if (bandOffsets == null) {
        throw new IllegalArgumentException("bandOffsets == null!");
      }
      int numBands = colorSpace.getNumComponents() +
          (hasAlpha ? 1 : 0);
      if (bandOffsets.length != numBands) {
        throw new IllegalArgumentException
            ("bandOffsets.length is wrong!");
      }
      if (dataType != DataBuffer.TYPE_BYTE &&
          dataType != DataBuffer.TYPE_SHORT &&
          dataType != DataBuffer.TYPE_USHORT &&
          dataType != DataBuffer.TYPE_INT &&
          dataType != DataBuffer.TYPE_FLOAT &&
          dataType != DataBuffer.TYPE_DOUBLE) {
        throw new IllegalArgumentException
            ("Bad value for dataType!");
      }
      this.colorSpace = colorSpace;
      this.bandOffsets = (int[]) bandOffsets.clone();
      this.dataType = dataType;
      this.hasAlpha = hasAlpha;
      this.isAlphaPremultiplied = isAlphaPremultiplied;

      this.colorModel =
          ImageTypeSpecifier.createComponentCM(colorSpace,
              bandOffsets.length,
              dataType,
              hasAlpha,
              isAlphaPremultiplied);

      int minBandOffset = bandOffsets[0];
      int maxBandOffset = minBandOffset;
      for (int i = 0; i < bandOffsets.length; i++) {
        int offset = bandOffsets[i];
        minBandOffset = Math.min(offset, minBandOffset);
        maxBandOffset = Math.max(offset, maxBandOffset);
      }
      int pixelStride = maxBandOffset - minBandOffset + 1;

      int w = 1;
      int h = 1;
      this.sampleModel =
          new PixelInterleavedSampleModel(dataType,
              w, h,
              pixelStride,
              w * pixelStride,
              bandOffsets);
    }

    public boolean equals(Object o) {
      if ((o == null) ||
          !(o instanceof ImageTypeSpecifier.Interleaved)) {
        return false;
      }

      ImageTypeSpecifier.Interleaved that =
          (ImageTypeSpecifier.Interleaved) o;

      if ((!(this.colorSpace.equals(that.colorSpace))) ||
          (this.dataType != that.dataType) ||
          (this.hasAlpha != that.hasAlpha) ||
          (this.isAlphaPremultiplied != that.isAlphaPremultiplied) ||
          (this.bandOffsets.length != that.bandOffsets.length)) {
        return false;
      }

      for (int i = 0; i < bandOffsets.length; i++) {
        if (this.bandOffsets[i] != that.bandOffsets[i]) {
          return false;
        }
      }

      return true;
    }

    public int hashCode() {
      return (super.hashCode() +
          (4 * bandOffsets.length) +
          (25 * dataType) +
          (hasAlpha ? 17 : 18));
    }
  }

  /**
   * Returns a specifier for an interleaved image format that will
   * use a <code>ComponentColorModel</code> and a
   * <code>PixelInterleavedSampleModel</code> to store each pixel
   * component in a separate byte, short, or int.
   *
   * @param colorSpace the desired <code>ColorSpace</code>.
   * @param bandOffsets an array of <code>int</code>s indicating the offsets for each band.
   * @param dataType the desired data type, as one of the enumerations from the
   * <code>DataBuffer</code> class.
   * @param hasAlpha <code>true</code> if an alpha channel is desired.
   * @param isAlphaPremultiplied <code>true</code> if the color channels will be premultipled by the
   * alpha channel.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>colorSpace</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>bandOffsets</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>dataType</code> is not one of the legal
   * <code>DataBuffer.TYPE_*</code> constants.
   * @throws IllegalArgumentException if <code>bandOffsets.length</code> does not equal the number
   * of color space components, plus 1 if <code>hasAlpha</code> is <code>true</code>.
   */
  public static ImageTypeSpecifier
  createInterleaved(ColorSpace colorSpace,
      int[] bandOffsets,
      int dataType,
      boolean hasAlpha,
      boolean isAlphaPremultiplied) {
    return new ImageTypeSpecifier.Interleaved(colorSpace,
        bandOffsets,
        dataType,
        hasAlpha,
        isAlphaPremultiplied);
  }

  // Banded

  static class Banded extends ImageTypeSpecifier {

    ColorSpace colorSpace;
    int[] bankIndices;
    int[] bandOffsets;
    int dataType;
    boolean hasAlpha;
    boolean isAlphaPremultiplied;

    public Banded(ColorSpace colorSpace,
        int[] bankIndices,
        int[] bandOffsets,
        int dataType,
        boolean hasAlpha,
        boolean isAlphaPremultiplied) {
      if (colorSpace == null) {
        throw new IllegalArgumentException("colorSpace == null!");
      }
      if (bankIndices == null) {
        throw new IllegalArgumentException("bankIndices == null!");
      }
      if (bandOffsets == null) {
        throw new IllegalArgumentException("bandOffsets == null!");
      }
      if (bankIndices.length != bandOffsets.length) {
        throw new IllegalArgumentException
            ("bankIndices.length != bandOffsets.length!");
      }
      if (dataType != DataBuffer.TYPE_BYTE &&
          dataType != DataBuffer.TYPE_SHORT &&
          dataType != DataBuffer.TYPE_USHORT &&
          dataType != DataBuffer.TYPE_INT &&
          dataType != DataBuffer.TYPE_FLOAT &&
          dataType != DataBuffer.TYPE_DOUBLE) {
        throw new IllegalArgumentException
            ("Bad value for dataType!");
      }
      int numBands = colorSpace.getNumComponents() +
          (hasAlpha ? 1 : 0);
      if (bandOffsets.length != numBands) {
        throw new IllegalArgumentException
            ("bandOffsets.length is wrong!");
      }

      this.colorSpace = colorSpace;
      this.bankIndices = (int[]) bankIndices.clone();
      this.bandOffsets = (int[]) bandOffsets.clone();
      this.dataType = dataType;
      this.hasAlpha = hasAlpha;
      this.isAlphaPremultiplied = isAlphaPremultiplied;

      this.colorModel =
          ImageTypeSpecifier.createComponentCM(colorSpace,
              bankIndices.length,
              dataType,
              hasAlpha,
              isAlphaPremultiplied);

      int w = 1;
      int h = 1;
      this.sampleModel = new BandedSampleModel(dataType,
          w, h,
          w,
          bankIndices,
          bandOffsets);
    }

    public boolean equals(Object o) {
      if ((o == null) ||
          !(o instanceof ImageTypeSpecifier.Banded)) {
        return false;
      }

      ImageTypeSpecifier.Banded that =
          (ImageTypeSpecifier.Banded) o;

      if ((!(this.colorSpace.equals(that.colorSpace))) ||
          (this.dataType != that.dataType) ||
          (this.hasAlpha != that.hasAlpha) ||
          (this.isAlphaPremultiplied != that.isAlphaPremultiplied) ||
          (this.bankIndices.length != that.bankIndices.length) ||
          (this.bandOffsets.length != that.bandOffsets.length)) {
        return false;
      }

      for (int i = 0; i < bankIndices.length; i++) {
        if (this.bankIndices[i] != that.bankIndices[i]) {
          return false;
        }
      }

      for (int i = 0; i < bandOffsets.length; i++) {
        if (this.bandOffsets[i] != that.bandOffsets[i]) {
          return false;
        }
      }

      return true;
    }

    public int hashCode() {
      return (super.hashCode() +
          (3 * bandOffsets.length) +
          (7 * bankIndices.length) +
          (21 * dataType) +
          (hasAlpha ? 19 : 29));
    }
  }

  /**
   * Returns a specifier for a banded image format that will use a
   * <code>ComponentColorModel</code> and a
   * <code>BandedSampleModel</code> to store each channel in a
   * separate array.
   *
   * @param colorSpace the desired <code>ColorSpace</code>.
   * @param bankIndices an array of <code>int</code>s indicating the bank in which each band will be
   * stored.
   * @param bandOffsets an array of <code>int</code>s indicating the starting offset of each band
   * within its bank.
   * @param dataType the desired data type, as one of the enumerations from the
   * <code>DataBuffer</code> class.
   * @param hasAlpha <code>true</code> if an alpha channel is desired.
   * @param isAlphaPremultiplied <code>true</code> if the color channels will be premultipled by the
   * alpha channel.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>colorSpace</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>bankIndices</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>bandOffsets</code> is <code>null</code>.
   * @throws IllegalArgumentException if the lengths of <code>bankIndices</code> and
   * <code>bandOffsets</code> differ.
   * @throws IllegalArgumentException if <code>bandOffsets.length</code> does not equal the number
   * of color space components, plus 1 if <code>hasAlpha</code> is <code>true</code>.
   * @throws IllegalArgumentException if <code>dataType</code> is not one of the legal
   * <code>DataBuffer.TYPE_*</code> constants.
   */
  public static ImageTypeSpecifier
  createBanded(ColorSpace colorSpace,
      int[] bankIndices,
      int[] bandOffsets,
      int dataType,
      boolean hasAlpha,
      boolean isAlphaPremultiplied) {
    return new ImageTypeSpecifier.Banded(colorSpace,
        bankIndices,
        bandOffsets,
        dataType,
        hasAlpha,
        isAlphaPremultiplied);
  }

  // Grayscale

  static class Grayscale extends ImageTypeSpecifier {

    int bits;
    int dataType;
    boolean isSigned;
    boolean hasAlpha;
    boolean isAlphaPremultiplied;

    public Grayscale(int bits,
        int dataType,
        boolean isSigned,
        boolean hasAlpha,
        boolean isAlphaPremultiplied) {
      if (bits != 1 && bits != 2 && bits != 4 &&
          bits != 8 && bits != 16) {
        throw new IllegalArgumentException("Bad value for bits!");
      }
      if (dataType != DataBuffer.TYPE_BYTE &&
          dataType != DataBuffer.TYPE_SHORT &&
          dataType != DataBuffer.TYPE_USHORT) {
        throw new IllegalArgumentException
            ("Bad value for dataType!");
      }
      if (bits > 8 && dataType == DataBuffer.TYPE_BYTE) {
        throw new IllegalArgumentException
            ("Too many bits for dataType!");
      }

      this.bits = bits;
      this.dataType = dataType;
      this.isSigned = isSigned;
      this.hasAlpha = hasAlpha;
      this.isAlphaPremultiplied = isAlphaPremultiplied;

      ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);

      if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) ||
          (bits == 16 &&
              (dataType == DataBuffer.TYPE_SHORT ||
                  dataType == DataBuffer.TYPE_USHORT))) {
        // Use component color model & sample model

        int numBands = hasAlpha ? 2 : 1;
        int transparency =
            hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE;

        int[] nBits = new int[numBands];
        nBits[0] = bits;
        if (numBands == 2) {
          nBits[1] = bits;
        }
        this.colorModel =
            new ComponentColorModel(colorSpace,
                nBits,
                hasAlpha,
                isAlphaPremultiplied,
                transparency,
                dataType);

        int[] bandOffsets = new int[numBands];
        bandOffsets[0] = 0;
        if (numBands == 2) {
          bandOffsets[1] = 1;
        }

        int w = 1;
        int h = 1;
        this.sampleModel =
            new PixelInterleavedSampleModel(dataType,
                w, h,
                numBands, w * numBands,
                bandOffsets);
      } else {
        int numEntries = 1 << bits;
        byte[] arr = new byte[numEntries];
        for (int i = 0; i < numEntries; i++) {
          arr[i] = (byte) (i * 255 / (numEntries - 1));
        }
        this.colorModel =
            new IndexColorModel(bits, numEntries, arr, arr, arr);

        this.sampleModel =
            new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
      }
    }
  }

  /**
   * Returns a specifier for a grayscale image format that will pack
   * pixels of the given bit depth into array elements of
   * the specified data type.
   *
   * @param bits the number of bits per gray value (1, 2, 4, 8, or 16).
   * @param dataType the desired data type, as one of the enumerations from the
   * <code>DataBuffer</code> class.
   * @param isSigned <code>true</code> if negative values are to be represented.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>bits</code> is not one of 1, 2, 4, 8, or 16.
   * @throws IllegalArgumentException if <code>dataType</code> is not one of
   * <code>DataBuffer.TYPE_BYTE</code>, <code>DataBuffer.TYPE_SHORT</code>, or
   * <code>DataBuffer.TYPE_USHORT</code>.
   * @throws IllegalArgumentException if <code>bits</code> is larger than the bit size of the given
   * <code>dataType</code>.
   */
  public static ImageTypeSpecifier
  createGrayscale(int bits,
      int dataType,
      boolean isSigned) {
    return new ImageTypeSpecifier.Grayscale(bits,
        dataType,
        isSigned,
        false,
        false);
  }

  /**
   * Returns a specifier for a grayscale plus alpha image format
   * that will pack pixels of the given bit depth into array
   * elements of the specified data type.
   *
   * @param bits the number of bits per gray value (1, 2, 4, 8, or 16).
   * @param dataType the desired data type, as one of the enumerations from the
   * <code>DataBuffer</code> class.
   * @param isSigned <code>true</code> if negative values are to be represented.
   * @param isAlphaPremultiplied <code>true</code> if the luminance channel will be premultipled by
   * the alpha channel.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>bits</code> is not one of 1, 2, 4, 8, or 16.
   * @throws IllegalArgumentException if <code>dataType</code> is not one of
   * <code>DataBuffer.TYPE_BYTE</code>, <code>DataBuffer.TYPE_SHORT</code>, or
   * <code>DataBuffer.TYPE_USHORT</code>.
   * @throws IllegalArgumentException if <code>bits</code> is larger than the bit size of the given
   * <code>dataType</code>.
   */
  public static ImageTypeSpecifier
  createGrayscale(int bits,
      int dataType,
      boolean isSigned,
      boolean isAlphaPremultiplied) {
    return new ImageTypeSpecifier.Grayscale(bits,
        dataType,
        isSigned,
        true,
        isAlphaPremultiplied);
  }

  // Indexed

  static class Indexed extends ImageTypeSpecifier {

    byte[] redLUT;
    byte[] greenLUT;
    byte[] blueLUT;
    byte[] alphaLUT = null;
    int bits;
    int dataType;

    public Indexed(byte[] redLUT,
        byte[] greenLUT,
        byte[] blueLUT,
        byte[] alphaLUT,
        int bits,
        int dataType) {
      if (redLUT == null || greenLUT == null || blueLUT == null) {
        throw new IllegalArgumentException("LUT is null!");
      }
      if (bits != 1 && bits != 2 && bits != 4 &&
          bits != 8 && bits != 16) {
        throw new IllegalArgumentException("Bad value for bits!");
      }
      if (dataType != DataBuffer.TYPE_BYTE &&
          dataType != DataBuffer.TYPE_SHORT &&
          dataType != DataBuffer.TYPE_USHORT &&
          dataType != DataBuffer.TYPE_INT) {
        throw new IllegalArgumentException
            ("Bad value for dataType!");
      }
      if ((bits > 8 && dataType == DataBuffer.TYPE_BYTE) ||
          (bits > 16 && dataType != DataBuffer.TYPE_INT)) {
        throw new IllegalArgumentException
            ("Too many bits for dataType!");
      }

      int len = 1 << bits;
      if (redLUT.length != len ||
          greenLUT.length != len ||
          blueLUT.length != len ||
          (alphaLUT != null && alphaLUT.length != len)) {
        throw new IllegalArgumentException("LUT has improper length!");
      }
      this.redLUT = (byte[]) redLUT.clone();
      this.greenLUT = (byte[]) greenLUT.clone();
      this.blueLUT = (byte[]) blueLUT.clone();
      if (alphaLUT != null) {
        this.alphaLUT = (byte[]) alphaLUT.clone();
      }
      this.bits = bits;
      this.dataType = dataType;

      if (alphaLUT == null) {
        this.colorModel = new IndexColorModel(bits,
            redLUT.length,
            redLUT,
            greenLUT,
            blueLUT);
      } else {
        this.colorModel = new IndexColorModel(bits,
            redLUT.length,
            redLUT,
            greenLUT,
            blueLUT,
            alphaLUT);
      }

      if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) ||
          (bits == 16 &&
              (dataType == DataBuffer.TYPE_SHORT ||
                  dataType == DataBuffer.TYPE_USHORT))) {
        int[] bandOffsets = {0};
        this.sampleModel =
            new PixelInterleavedSampleModel(dataType,
                1, 1, 1, 1,
                bandOffsets);
      } else {
        this.sampleModel =
            new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
      }
    }
  }

  /**
   * Returns a specifier for an indexed-color image format that will pack
   * index values of the given bit depth into array elements of
   * the specified data type.
   *
   * @param redLUT an array of <code>byte</code>s containing the red values for each index.
   * @param greenLUT an array of <code>byte</code>s containing * the green values for each index.
   * @param blueLUT an array of <code>byte</code>s containing the blue values for each index.
   * @param alphaLUT an array of <code>byte</code>s containing the alpha values for each index, or
   * <code>null</code> to create a fully opaque LUT.
   * @param bits the number of bits in each index.
   * @param dataType the desired output type, as one of the enumerations from the
   * <code>DataBuffer</code> class.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>redLUT</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>greenLUT</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>blueLUT</code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>bits</code> is not one of 1, 2, 4, 8, or 16.
   * @throws IllegalArgumentException if the non-<code>null</code> LUT parameters do not have
   * lengths of exactly {@code 1 << bits}.
   * @throws IllegalArgumentException if <code>dataType</code> is not one of
   * <code>DataBuffer.TYPE_BYTE</code>, <code>DataBuffer.TYPE_SHORT</code>,
   * <code>DataBuffer.TYPE_USHORT</code>, or <code>DataBuffer.TYPE_INT</code>.
   * @throws IllegalArgumentException if <code>bits</code> is larger than the bit size of the given
   * <code>dataType</code>.
   */
  public static ImageTypeSpecifier
  createIndexed(byte[] redLUT,
      byte[] greenLUT,
      byte[] blueLUT,
      byte[] alphaLUT,
      int bits,
      int dataType) {
    return new ImageTypeSpecifier.Indexed(redLUT,
        greenLUT,
        blueLUT,
        alphaLUT,
        bits,
        dataType);
  }

  /**
   * Returns an <code>ImageTypeSpecifier</code> that encodes
   * one of the standard <code>BufferedImage</code> types
   * (other than <code>TYPE_CUSTOM</code>).
   *
   * @param bufferedImageType an int representing one of the standard <code>BufferedImage</code>
   * types.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>bufferedImageType</code> is not one of the standard
   * types, or is equal to <code>TYPE_CUSTOM</code>.
   * @see java.awt.image.BufferedImage
   * @see java.awt.image.BufferedImage#TYPE_INT_RGB
   * @see java.awt.image.BufferedImage#TYPE_INT_ARGB
   * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE
   * @see java.awt.image.BufferedImage#TYPE_INT_BGR
   * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR
   * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR
   * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE
   * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB
   * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB
   * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY
   * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY
   * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY
   * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED
   */
  public static ImageTypeSpecifier createFromBufferedImageType(int bufferedImageType) {
    if (bufferedImageType >= BufferedImage.TYPE_INT_RGB &&
        bufferedImageType <= BufferedImage.TYPE_BYTE_INDEXED) {
      return getSpecifier(bufferedImageType);
    } else if (bufferedImageType == BufferedImage.TYPE_CUSTOM) {
      throw new IllegalArgumentException("Cannot create from TYPE_CUSTOM!");
    } else {
      throw new IllegalArgumentException("Invalid BufferedImage type!");
    }
  }

  /**
   * Returns an <code>ImageTypeSpecifier</code> that encodes the
   * layout of a <code>RenderedImage</code> (which may be a
   * <code>BufferedImage</code>).
   *
   * @param image a <code>RenderedImage</code>.
   * @return an <code>ImageTypeSpecifier</code> with the desired characteristics.
   * @throws IllegalArgumentException if <code>image</code> is <code>null</code>.
   */
  public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
    if (image == null) {
      throw new IllegalArgumentException("image == null!");
    }

    if (image instanceof BufferedImage) {
      int bufferedImageType = ((BufferedImage) image).getType();
      if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
        return getSpecifier(bufferedImageType);
      }
    }

    return new ImageTypeSpecifier(image);
  }

  /**
   * Returns an int containing one of the enumerated constant values
   * describing image formats from <code>BufferedImage</code>.
   *
   * @return an <code>int</code> representing a <code>BufferedImage</code> type.
   * @see java.awt.image.BufferedImage
   * @see java.awt.image.BufferedImage#TYPE_CUSTOM
   * @see java.awt.image.BufferedImage#TYPE_INT_RGB
   * @see java.awt.image.BufferedImage#TYPE_INT_ARGB
   * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE
   * @see java.awt.image.BufferedImage#TYPE_INT_BGR
   * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR
   * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR
   * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE
   * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB
   * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB
   * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY
   * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY
   * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY
   * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED
   */
  public int getBufferedImageType() {
    BufferedImage bi = createBufferedImage(1, 1);
    return bi.getType();
  }

  /**
   * Return the number of color components
   * specified by this object.  This is the same value as returned by
   * <code>ColorModel.getNumComponents</code>
   *
   * @return the number of components in the image.
   */
  public int getNumComponents() {
    return colorModel.getNumComponents();
  }

  /**
   * Return the number of bands
   * specified by this object.  This is the same value as returned by
   * <code>SampleModel.getNumBands</code>
   *
   * @return the number of bands in the image.
   */
  public int getNumBands() {
    return sampleModel.getNumBands();
  }

  /**
   * Return the number of bits used to represent samples of the given band.
   *
   * @param band the index of the band to be queried, as an int.
   * @return an int specifying a number of bits.
   * @throws IllegalArgumentException if <code>band</code> is negative or greater than the largest
   * band index.
   */
  public int getBitsPerBand(int band) {
    if (band < 0 | band >= getNumBands()) {
      throw new IllegalArgumentException("band out of range!");
    }
    return sampleModel.getSampleSize(band);
  }

  /**
   * Returns a <code>SampleModel</code> based on the settings
   * encapsulated within this object.  The width and height of the
   * <code>SampleModel</code> will be set to arbitrary values.
   *
   * @return a <code>SampleModel</code> with arbitrary dimensions.
   */
  public SampleModel getSampleModel() {
    return sampleModel;
  }

  /**
   * Returns a <code>SampleModel</code> based on the settings
   * encapsulated within this object.  The width and height of the
   * <code>SampleModel</code> will be set to the supplied values.
   *
   * @param width the desired width of the returned <code>SampleModel</code>.
   * @param height the desired height of the returned <code>SampleModel</code>.
   * @return a <code>SampleModel</code> with the given dimensions.
   * @throws IllegalArgumentException if either <code>width</code> or <code>height</code> are
   * negative or zero.
   * @throws IllegalArgumentException if the product of <code>width</code> and <code>height</code>
   * is greater than <code>Integer.MAX_VALUE</code>
   */
  public SampleModel getSampleModel(int width, int height) {
    if ((long) width * height > Integer.MAX_VALUE) {
      throw new IllegalArgumentException
          ("width*height > Integer.MAX_VALUE!");
    }
    return sampleModel.createCompatibleSampleModel(width, height);
  }

  /**
   * Returns the <code>ColorModel</code> specified by this object.
   *
   * @return a <code>ColorModel</code>.
   */
  public ColorModel getColorModel() {
    return colorModel;
  }

  /**
   * Creates a <code>BufferedImage</code> with a given width and
   * height according to the specification embodied in this object.
   *
   * @param width the desired width of the returned <code>BufferedImage</code>.
   * @param height the desired height of the returned <code>BufferedImage</code>.
   * @return a new <code>BufferedImage</code>
   * @throws IllegalArgumentException if either <code>width</code> or <code>height</code> are
   * negative or zero.
   * @throws IllegalArgumentException if the product of <code>width</code> and <code>height</code>
   * is greater than <code>Integer.MAX_VALUE</code>, or if the number of array elements needed to
   * store the image is greater than <code>Integer.MAX_VALUE</code>.
   */
  public BufferedImage createBufferedImage(int width, int height) {
    try {
      SampleModel sampleModel = getSampleModel(width, height);
      WritableRaster raster =
          Raster.createWritableRaster(sampleModel,
              new Point(0, 0));
      return new BufferedImage(colorModel, raster,
          colorModel.isAlphaPremultiplied(),
          new Hashtable());
    } catch (NegativeArraySizeException e) {
      // Exception most likely thrown from a DataBuffer constructor
      throw new IllegalArgumentException
          ("Array size > Integer.MAX_VALUE!");
    }
  }

  /**
   * Returns <code>true</code> if the given <code>Object</code> is
   * an <code>ImageTypeSpecifier</code> and has a
   * <code>SampleModel</code> and <code>ColorModel</code> that are
   * equal to those of this object.
   *
   * @param o the <code>Object</code> to be compared for equality.
   * @return <code>true</code> if the given object is an equivalent <code>ImageTypeSpecifier</code>.
   */
  public boolean equals(Object o) {
    if ((o == null) || !(o instanceof ImageTypeSpecifier)) {
      return false;
    }

    ImageTypeSpecifier that = (ImageTypeSpecifier) o;
    return (colorModel.equals(that.colorModel)) &&
        (sampleModel.equals(that.sampleModel));
  }

  /**
   * Returns the hash code for this ImageTypeSpecifier.
   *
   * @return a hash code for this ImageTypeSpecifier
   */
  public int hashCode() {
    return (9 * colorModel.hashCode()) + (14 * sampleModel.hashCode());
  }

  private static ImageTypeSpecifier getSpecifier(int type) {
    if (BISpecifier[type] == null) {
      BISpecifier[type] = createSpecifier(type);
    }
    return BISpecifier[type];
  }

  private static ImageTypeSpecifier createSpecifier(int type) {
    switch (type) {
      case BufferedImage.TYPE_INT_RGB:
        return createPacked(sRGB,
            0x00ff0000,
            0x0000ff00,
            0x000000ff,
            0x0,
            DataBuffer.TYPE_INT,
            false);

      case BufferedImage.TYPE_INT_ARGB:
        return createPacked(sRGB,
            0x00ff0000,
            0x0000ff00,
            0x000000ff,
            0xff000000,
            DataBuffer.TYPE_INT,
            false);

      case BufferedImage.TYPE_INT_ARGB_PRE:
        return createPacked(sRGB,
            0x00ff0000,
            0x0000ff00,
            0x000000ff,
            0xff000000,
            DataBuffer.TYPE_INT,
            true);

      case BufferedImage.TYPE_INT_BGR:
        return createPacked(sRGB,
            0x000000ff,
            0x0000ff00,
            0x00ff0000,
            0x0,
            DataBuffer.TYPE_INT,
            false);

      case BufferedImage.TYPE_3BYTE_BGR:
        return createInterleaved(sRGB,
            new int[]{2, 1, 0},
            DataBuffer.TYPE_BYTE,
            false,
            false);

      case BufferedImage.TYPE_4BYTE_ABGR:
        return createInterleaved(sRGB,
            new int[]{3, 2, 1, 0},
            DataBuffer.TYPE_BYTE,
            true,
            false);

      case BufferedImage.TYPE_4BYTE_ABGR_PRE:
        return createInterleaved(sRGB,
            new int[]{3, 2, 1, 0},
            DataBuffer.TYPE_BYTE,
            true,
            true);

      case BufferedImage.TYPE_USHORT_565_RGB:
        return createPacked(sRGB,
            0xF800,
            0x07E0,
            0x001F,
            0x0,
            DataBuffer.TYPE_USHORT,
            false);

      case BufferedImage.TYPE_USHORT_555_RGB:
        return createPacked(sRGB,
            0x7C00,
            0x03E0,
            0x001F,
            0x0,
            DataBuffer.TYPE_USHORT,
            false);

      case BufferedImage.TYPE_BYTE_GRAY:
        return createGrayscale(8,
            DataBuffer.TYPE_BYTE,
            false);

      case BufferedImage.TYPE_USHORT_GRAY:
        return createGrayscale(16,
            DataBuffer.TYPE_USHORT,
            false);

      case BufferedImage.TYPE_BYTE_BINARY:
        return createGrayscale(1,
            DataBuffer.TYPE_BYTE,
            false);

      case BufferedImage.TYPE_BYTE_INDEXED: {

        BufferedImage bi =
            new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED);
        IndexColorModel icm = (IndexColorModel) bi.getColorModel();
        int mapSize = icm.getMapSize();
        byte[] redLUT = new byte[mapSize];
        byte[] greenLUT = new byte[mapSize];
        byte[] blueLUT = new byte[mapSize];
        byte[] alphaLUT = new byte[mapSize];

        icm.getReds(redLUT);
        icm.getGreens(greenLUT);
        icm.getBlues(blueLUT);
        icm.getAlphas(alphaLUT);

        return createIndexed(redLUT, greenLUT, blueLUT, alphaLUT,
            8,
            DataBuffer.TYPE_BYTE);
      }
      default:
        throw new IllegalArgumentException("Invalid BufferedImage type!");
    }
  }

}
